Formatting of ASP.NET Core WebAPI response data 您所在的位置:网站首页 aspnet core webapi Formatting of ASP.NET Core WebAPI response data

Formatting of ASP.NET Core WebAPI response data

2022-12-28 06:15| 来源: 网络整理| 查看: 265

Formatting of ASP.NET Core WebAPI response data

Original reference link: https://docs.microsoft.com/zh-cn/aspnet/core/web-api/advanced/formatting?view=aspnetcore-2.2

When a Web API controller action returns a result, the result can be formatted.

format-specific result of the operation

The types of the results of such operations are specific to special formats, such as JsonResult and ContentResult .

Action results that return objects of type other than IActionResult will be serialized using the corresponding IOutputFormatter implementation.

For important actions that have multiple return types or options (such as different HTTP status codes based on the result of the action performed), prefer IActionResult as the return type.

Content Negotiation _

Content negotiation, or conneg for short, occurs when the client specifies an Accept header.

The default format used by ASP.NET Core MVC is JSON.

Content negotiation is implemented by ObjectResult and is also built into the status code-specific operation results returned from helper methods (all based on ObjectResult ). It is also possible to return a model type (a class that has been defined as a data transfer type), and the framework will automatically package it in an ObjectResult .

For example, the OK() and NotFound() helper methods are used in the following code :

[HttpGet("Search")] public IActionResult Search(string namelike) { if (_context. TodoItems. Any(a => a. Name == namelike)) { return Ok(); } return NotFound(namelike); }

Both methods will return a response in JSON format.

By default, ASP.NET Core MVC only supports JSON . So, even if another format is specified, the returned result is still in JSON format. (This behavior can be changed by configuring the formatter, but in most cases, no formatting configuration is required)

Even if the controller action returns a plain class object (such as Student ), in this case ASP.NET Core MVC will automatically create the ObjectResult that wraps the object . The client will get a formatted serialized object (JSON format by default, XML or other formats can be configured). If the returned object is null , then the framework will return a 204 No Content response.

E.g:

public Student Get(string stuNo) { return GetByStudent(stuNo); }

In the above code, if a valid Student is obtained, a "200 Normal" response will be returned, and if a null value is obtained, a "204 No Content" response will be returned.

Note: This example further illustrates that different HTTP status codes are specific to different scenarios. In practice, you should combine the corresponding scenarios and use the method that returns IActionResult corresponding to the HTTP status code with semantics specific to this scenario.

Content Negotiation Process

Content negotiation only happens when the Accept header is present in the request. When a request includes an accept header, the framework enumerates the media types in the accept header in the best order and tries to find a formatter that can produce a response in the format specified by the accept header. If no formatter is found that can satisfy the client's request, the framework will try to find the first formatter that can generate a response (unless the developer configures an option on MvcOptions to return "406 Not Acceptable"). If the request specifies XML, but no XML formatter is configured, the JSON formatter will be used. In general, if no formatter is configured that can provide the requested format, then the first formatter that can format the object is used. If no headers are provided, the response will be serialized using the first formatter that can handle the object being returned. In this case, no negotiation takes place - the server determines which format will be used.

illustrate:

If the Accept header contains */* , it will be ignored unless RespectBrowserAcceptHeader is set to true on MvcOptions .

Browsers and Content Negotiation

Unlike traditional API clients, web browsers tend to provide Accept headers that include various formats (including wildcards). By default, when the framework detects that a request is coming from a browser, it ignores the Accept header and returns content in the application's configured default format (JSON, unless configured otherwise). This provides a more consistent experience when using the API across browsers.

If it is preferred that the application obey the browser accept header, this can be configured as part of the MVC configuration by setting RespectBrowserAcceptHeader to true in the ConfigureServices method in Startup.cs .

services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; // default is false }); Configure the formatter

If your application needs to support formats other than the default JSON format, you can add NuGet packages and configure MVC to support them.

The formatters for input and output are different: input formatters are used by model binding; output formatters are used to format responses.

Add XML format support

To add support for XML formats, install the Microsoft.AspNetCore.Mvc.Formatters.Xml NuGet package.

Add XmlSerializerFormatters to the MVC configuration in Startup.cs :

services. AddMvc() .AddXmlSerializerFormatters();

Alternatively, it is possible to add only the output formatter:

services.AddMvc(options => { options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); });

These two methods will use System.Xml.Serialization.XmlSerializer to serialize the result. If you prefer, you can use System.Runtime.Serialization.DataContractSerializer by adding the associated formatter :

services.AddMvc(options => { options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); }); enforce a specific format

The [Produces] filter can be applied if it is desired to restrict the response format to a specific operation . The [Produces] filter specifies the response format for a specific action (or controller). Like most filters, this can be applied at the action level, at the controller level, or globally.

[Produces("application/json")] public class AuthorsController

The [Produces] filter will force all actions in the AuthorsController to return JSON -formatted responses, even if the application has been configured with other formatters and the client provides an Accept header requesting other available formats.

special case formatter

Some special cases are implemented using built-in formatters. By default, string return types will be formatted as text/plain ( or text/html if requested via the Accept header ). This behavior can be removed by removing the TextOutputFormatter . Operations that have a model object return type will return a "204 No Content" response when null is returned. This behavior can be removed by removing the HttpNoContentOutputFormatter . (see example below)

In Startup.cs delete the formatter through the Configure method, the following code deletes TextOutputFormatter and HttpNoContentOutputFormatter :

services.AddMvc(options => { options.OutputFormatters.RemoveType(); options.OutputFormatters.RemoveType(); });

Without a TextOutputFormatter , the string return type will return something like "406 Not Acceptable". Note that when the TextOutputFormatter is removed it will format the string return type if the XML formatter is present.

Without HttpNoContentOutputFormatter , the null object will be formatted using the configured formatter. For example, a JSON formatter will only return a response with a null body , while an XML formatter will return an empty XML element with the attribute set to xsi:nil="true" .

Response format URL file extension mapping

Clients can require a specific format as part of the URL, such as in a query string or as part of the path, or by using a format-specific file extension such as .xml or .json. The mapping of request paths must be specified in the routes used by the API. E.g:

[FormatFilter] public class ProductsController { [Route("[controller]/[action]/{id}.{format?}")] public Product GetById(int id)

This route will allow the requested format to be specified as an optional file extension. The [FormatFilter] attribute checks for the presence of a format value in RouteData and maps the response format to the corresponding formatter when the response is created.

routingformatter/products/GetById/5default output formatter/products/GetById/5.jsonJSON formatter (like configuration)/products/GetById/5.xmlXML formatters (like configuration) custom formatter

By default, ASP.NET Core MVC provides built-in support for data exchange in Web API using JSON or XML. Support for other formats can be added by creating custom formatters. For example, in the content negotiation mentioned above, if you want to support other content types that are not supported by the built-in formatters (JSON and XML) during content negotiation, you can use a custom formatter.

Steps to Create and Use a Custom Formatter Create an output formatter class if you want to serialize the data sent by the server sideCreate an input formatter class if you want to deserialize the data received on the server sideAdd an instance of the formatter to the InputFormatters and OutputFormatters collections in MvcOptions Create a custom formatter class

To create a formatter, you need to do the following:

Create a program class that derives from the corresponding base classSpecify a valid media type and encoding in the constructorOverride CanReadType / CanWriteType (or CanWriteResult) methodsOverride the ReadRequestBodyAsync / WriteResponseBodyAsync method Step 1: Create a program class derived from the corresponding base class

Text type: derived from the TextInputFormatter or TextOutputFormatter base class. E.g:

public class VcardOutputFormatter : TextOutputFormatter or public class VcardInputFormatter : TextInputFormatter

Binary type: derived from the InputFormatter or OutputFormatter base class.

Step 2: In the constructor, specify a valid media type and encoding

In the constructor, specify valid media types and encodings by adding to the SupportedMediaTypes and SupportedEncodings collections.

VcardOutputFormatter.cs:

public class VcardOutputFormatter : TextOutputFormatter { public VcardOutputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings. Add(Encoding. UTF8); SupportedEncodings. Add(Encoding. Unicode); } }

VcardInputFormatter.cs:

public class VcardInputFormatter : TextInputFormatter { public VcardInputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings. Add(Encoding. UTF8); SupportedEncodings. Add(Encoding. Unicode); } }

**NOTE:** Constructor dependency injection cannot be performed in formatter classes. For example, a logger cannot be obtained by adding a logger parameter to the constructor. To access the service, the context object passed to the method must be used. The code sample below (see step 4) shows how to do this.

Step 3: Override the CanReadType/CanWriteType (or CanWriteResult ) method

Specify the type that can be deserialized to, or serialized from, by overriding the CanReadType or CanWriteType methods.

The following example specifies that vCard text can only be created from the Contact type , and vice versa:

VcardOutputFormatter.cs:

public class VcardOutputFormatter : TextOutputFormatter { public VcardOutputFormatter() { ...//see code above } protected override bool CanWriteType(Type type) { if (typeof(Contact).IsAssignableFrom(type) || typeof(IEnumerable).IsAssignableFrom(type)) { return base. CanWriteType(type); } return false; } }

VcardInputFormatter.cs:

public class VcardInputFormatter : TextInputFormatter { public VcardInputFormatter() { ...//see above } protected override bool CanReadType(Type type) { if (type == typeof(Contact)) { return base. CanReadType(type); } return false; } } CanWriteResult method

In some cases, CanWriteResult must be overridden instead of CanWriteType . CanWriteResult is used if both of the following conditions are true :

The action method returns the model class.Has derived classes that may be returned at runtime.Need to know which derived class the operation returned at runtime.

For example, suppose an action method signature returns a Person type, but it might return a Student or Instructor subtype derived from Person . If you want the formatter to only work with Student objects, check the OutputFormatterCanWriteContext type in the context object supplied to the CanWriteResult method. Note that it is not necessary to use CanWriteResult when the action method returns an IActionResult ; in this case, the CanWriteType method accepts the runtime type.

Step 4: Rewrite the ReadRequestBodyAsync / WriteResponseBodyAsync method

The actual deserialization or serialization work is performed in ReadRequestBodyAsync or WriteResponseBodyAsync.

The following code shows how to get services from a dependency injection container (you can't get them from constructor arguments).

VcardOutputFormatter.cs:

public class VcardOutputFormatter : TextOutputFormatter { public VcardOutputFormatter() { ...//see above } protected override bool CanWriteType(Type type) { ...//see above } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { IServiceProvider serviceProvider = context.HttpContext.RequestServices; var logger = serviceProvider. GetService(typeof(ILogger)) as ILogger; var response = context.HttpContext.Response; var buffer = new StringBuilder(); if (context. Object is IEnumerable) { foreach (Contact contact in context. Object as IEnumerable) { FormatVcard(buffer, contact, logger); } } else { var contact = context. Object as Contact; FormatVcard(buffer, contact, logger); } await response. WriteAsync(buffer. ToString()); } private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger) { buffer.AppendLine("BEGIN:VCARD"); buffer.AppendLine("VERSION:2.1"); buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n"); buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n"); buffer.AppendFormat($"UID:{contact.ID}\r\n"); buffer.AppendLine("END:VCARD"); logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}"); } }

VcardInputFormatter.cs:

public class VcardInputFormatter : TextInputFormatter { public VcardInputFormatter() { ...//see above } protected override bool CanReadType(Type type) { ...//see above } public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (effectiveEncoding == null) { throw new ArgumentNullException(nameof(effectiveEncoding)); } var request = context.HttpContext.Request; using (var reader = new StreamReader(request.Body, effectiveEncoding)) { try { await ReadLineAsync("BEGIN:VCARD", reader, context); await ReadLineAsync("VERSION:2.1", reader, context); var nameLine = await ReadLineAsync("N:", reader, context); var split = nameLine.Split(";".ToCharArray()); var contact = new Contact() { LastName = split[0]. Substring(2), FirstName = split[1] }; await ReadLineAsync("FN:", reader, context); var idLine = await ReadLineAsync("UID:", reader, context); contact.ID = idLine.Substring(4); await ReadLineAsync("END:VCARD", reader, context); return await InputFormatterResult.SuccessAsync(contact); } catch { return await InputFormatterResult. FailureAsync(); } } } private async Task ReadLineAsync(string expectedText, StreamReader reader, InputFormatterContext context) { var line = await reader. ReadLineAsync(); if (!line. StartsWith(expectedText)) { var errorMessage = $"Looked for '{expectedText}' and got '{line}'"; context.ModelState.TryAddModelError(context.ModelName, errorMessage); throw new Exception(errorMessage); } return line; } } Configure MVC to use a custom formatter

To use a custom formatter, you need to add an instance of the formatter class to the InputFormatters or OutputFormatters collection.

services.AddMvc(options => { options.InputFormatters.Insert(0, new VcardInputFormatter()); options.OutputFormatters.Insert(0, new VcardOutputFormatter()); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Formatters are evaluated in the order of their insertion. The first one takes precedence.

Instructions for running the program:

To see the vCard output, run the application and send a request to http://localhost:63313/api/contacts/ (when running from Visual Studio) or http://localhost:5000/api/contacts/ (from the command line) runtime) to send a Get request with an Accept header of "text/vcard". To add a vCard to the in-memory contacts collection, send a Post request to the same URL with a Content-Type header of "text/vcard" and the vCard text in the body, formatted similarly to the example above.



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有